/*
 	Tool for Path Finding Algorithm Testing (ToPFAT) 
 	
    Copyright (C) 2009, Teemu Turunen and Arttu Viljakainen 
    Published under GNU General Public License V3 
    (see http://www.gnu.org/licenses/licenses.html)
    
    This file is part of Tool for Path Finding Algorithm Testing.

    Tool for Path Finding Algorithm Testing is free software: you can 
    redistribute it and/or modify it under the terms of the GNU General 
    Public License as published by the Free Software Foundation, 
    either version 3 of the License, or (at your option) any later version.

    Tool for Path Finding Algorithm Testing is distributed in the hope that it
    will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General 
    Public License for more details.

    You should have received a copy of the GNU General Public License
    along with Tool for Path Finding Algorithm Testing. If not, see         <http://www.gnu.org/licenses/>.
 */

package algorithms.dijkstra;

import java.util.Vector;

import algorithms.DescriptedPoint;

/**
 * Node class to be used in Dijkstra's algorithm.
 * 
 * @author arttu viljakainen
 *
 */
public class DNode implements Comparable<DNode> {
	private DescriptedPoint location;
	private Vector<DNode> neighbours;
	private int type;
	private int distanceFromStart;
	
	public DNode(int rowIndex, int columnIndex, int type) {
		location = new DescriptedPoint(columnIndex, rowIndex);
		neighbours = new Vector<DNode>();
		this.type = type;
		distanceFromStart = Integer.MAX_VALUE;
	}
	
	/**
	 * Adds neighbours for the node.
	 * 
	 * @param map
	 * @param allNodes
	 */
	public void addNodeNeighbours(int[][] map, Vector<DNode> allNodes) {
		for (int nRow = Math.max(0, location.y - 1); nRow <= Math.min(location.y + 1, map.length-1); nRow++) {
			for (int nColumn = Math.max(0, this.location.x-1); nColumn <= Math.min(this.location.x+1, map[0].length-1); nColumn++) {
				addNeighbourIfAdjacent(map, allNodes, nRow, nColumn);
			}
		}
		for (int nRow = Math.max(0, location.y - 1); nRow <= Math.min(location.y + 1, map.length-1); nRow++) {
			for (int nColumn = Math.max(0, this.location.x-1); nColumn <= Math.min(this.location.x+1, map[0].length-1); nColumn++) {
				addNeighbourIfDiagonal(map, allNodes, nRow, nColumn);
			}
		}
	}

	/**
	 * Adds neighbours that are adjacent to the node.
	 * 
	 * @param map
	 * @param allNodes
	 * @param nRow
	 * @param nColumn
	 */
	private void addNeighbourIfAdjacent(int[][] map, Vector<DNode> allNodes, int nRow, int nColumn) {
		if(this.location.x == nColumn || this.location.y == nRow) {
			DNode neighbour = allNodes.get(allNodes.indexOf(new DNode(nRow, nColumn, map[nRow][nColumn])));
			if(! this.equals(neighbour) && neighbour.type == 0) {					
				if(! neighbours.contains(neighbour))
					neighbours.add(neighbour);
			}
		}
	}
	
	/**
	 * Adds neighbours that are diagonally adjacent to the node.
	 * 
	 * @param map
	 * @param allNodes
	 * @param nRow
	 * @param nColumn
	 */
	private void addNeighbourIfDiagonal(int[][] map, Vector<DNode> allNodes, int nRow, int nColumn) {
		if(this.location.x != nColumn && this.location.y != nRow) {
			DNode neighbour = allNodes.get(allNodes.indexOf(new DNode(nRow, nColumn, map[nRow][nColumn])));
			boolean isOk = false;
			if(nRow > location.y && nColumn > location.x) {
				if(isThereAEastNeighbour() &&
						isThereASouthNeighbour())
							isOk = true;
			} else if(nRow > location.y && nColumn < location.x) {
				if(isThereASouthNeighbour() &&
						isThereAWestNeighbour())
							isOk = true;
			} else if(nRow < location.y && nColumn < location.x) {
				if(isThereAWestNeighbour() &&
						isThereANorthNeighbour())
							isOk = true;
			} else if(nRow < location.y && nColumn > location.x) {
				if(isThereANorthNeighbour() &&
						isThereAEastNeighbour())
							isOk = true;
			}
			
			if(! this.equals(neighbour) && neighbour.type == 0 && isOk) {					
				if(! neighbours.contains(neighbour))
					neighbours.add(neighbour);
			}
		}
	}
	
	
	private boolean isThereANorthNeighbour() {
		return neighbours.contains(new DNode(location.y - 1, location.x, 0));
	}
	
	private boolean isThereAEastNeighbour() {
		return neighbours.contains(new DNode(location.y, location.x + 1, 0));
	}

	private boolean isThereASouthNeighbour() {
		return neighbours.contains(new DNode(location.y + 1, location.x, 0));
	}

	private boolean isThereAWestNeighbour() {
		return neighbours.contains(new DNode(location.y, location.x - 1, 0));
	}
	
		
	@Override
	public boolean equals(Object obj) {
		if(! (obj instanceof DNode)) 
			return false;
		
		return ((DNode)obj).getLocation().equals(location);
	}
	
	@Override
	public int hashCode() {
		return this.location.hashCode();
	}
	
	@Override
	public int compareTo(DNode object) {
		
		if (object == this) 
			return 0;

		if (this.getDistanceFromStart() < object.getDistanceFromStart()) 
			return -1;
		else if (this.getDistanceFromStart() > object.getDistanceFromStart()) 
			return 1;
		else 
			return 0;
	}

	public Vector<DNode> getNeighbours() {
		return neighbours;
	}

	public DescriptedPoint getLocation() {
		return location;
	}
	
	public int getDistanceFromStart() {
		return distanceFromStart;
	}

	public void setDistanceFromStart(int distanceFromStart) {
		this.distanceFromStart = distanceFromStart;
	}

	public int getType() {
		return type;
	}

	public String toString() {
		return this.location.toString() + " type " + this.type + " distance " + this.distanceFromStart;
	}

}
